using System;
using System.Diagnostics;
using TestCases.CompareAssert.Core.Comparers.Basic;
using TestCases.CompareAssert.Core.Comparers.Collections;
using TestCases.CompareAssert.Core.Comparers.Composite;
using TestCases.CompareAssert.Core.Comparers.Generics;
using TestCases.CompareAssert.Core.Comparers.Interfaces;
using TestCases.CompareAssert.Core.Comparers.Members;
using TestCases.CompareAssert.Core.Comparers.Stateless;
using TestCases.CompareAssert.Core.Interfaces;
using TestCases.CompareAssert.Settings.Interfaces;

namespace TestCases.CompareAssert.Core.Comparers
{
    public class CustomizableComparer: IObjectComparer
    {
        public const string IdentifiableName = "CustomizableComparer";
        
        private readonly ICompareSettings _settings;
        private readonly IObjectComparer _defaultComparer;
              
        public CustomizableComparer(ICompareSettings settings)
        {
            _settings = settings;

            _defaultComparer = CompositeComparer.Single(

                NullComparer.Instance,
                new SetupComparer<ReferenceComparer>(SetupReferenceComparer, ReferenceComparer.Instance),
                CompositeComparer.All(

                    new SetupComparer<TypeComparer>(SetupDisabledComparer, TypeComparer.Instance),
                    new SetupComparer<HashCodeComparer>(SetupHashCodeComparer, HashCodeComparer.Instance),

                    /* All comparers which can return NotApplicable must be in Single compositor */
                    CompositeComparer.Single(
                        new SetupComparer<EqualsComparer>(SetupEqualsComparer, EqualsComparer.Instance),

                        new SetupComparer<SideIndependentComparer<EquatableComparer>>((x, y) => SetupGenericsComparer(x.Comparer, y),
                            new SideIndependentComparer<EquatableComparer>(new EquatableComparer())),

                        new SetupComparer<SideIndependentComparer<ComparableComparer>>((x, y) => SetupGenericsComparer(x.Comparer, y),
                            new SideIndependentComparer<ComparableComparer>(new ComparableComparer())),

                        new SetupComparer<DictionaryComparer>(SetupDictionaryComparer,
                            new DictionaryComparer(this) {IgnoreEntry = _settings.IsIgnoredEntry}),

                        new SetupComparer<ArraysComparer>(SetupDisabledComparer,
                            new ArraysComparer(this) {IgnoreElementAt = _settings.IsIgnoredElement}),

                        new SetupComparer<CollectionsComparer>(SetupGenericsComparer,
                            new CollectionsComparer(this) {IgnoreElementAt = _settings.IsIgnoredElement}),

                        new SetupComparer<EnumerableComparer>(SetupDisabledComparer,
                            new EnumerableComparer(this) {IgnoreElementAt = _settings.IsIgnoredElement}),

                        new CircularReferencesComparer(
                            CompositeComparer.Single(

                                new SetupComparer<PropertiesComparer>(SetupPropertiesComparer,
                                    new PropertiesComparer(this) {IgnoreMember = _settings.IsIgnoredProperty}),

                                new SetupComparer<FieldsComparer>(SetupFieldsComparer,
                                    new FieldsComparer(this) {IgnoreMember = _settings.IsIgnoredField})

                                )
                            )
                        )
                    )
                );                                           
        }

        private SetupStatus SetupHashCodeComparer(HashCodeComparer comparer, ICompareContext context)
        {
            return (_settings.IsHashCodeComparerForced) 
                ? SetupStatus.Default 
                : SetupEscapedComparer(comparer, context);
        }

        private SetupStatus SetupReferenceComparer(ReferenceComparer comparer, ICompareContext context)
        {
            return context.LValue is Type && context.RValue is Type
                ? SetupStatus.Default 
                : SetupEscapedComparer(comparer, context);
        }

        private SetupStatus SetupEqualsComparer(EqualsComparer comparer, ICompareContext context)
        {
            return (_settings.IsEqualsComparerForced)
                || (context.LType == context.RType && DefaultComparer.IsSimpleType(context.LType))
                ? SetupStatus.Default 
                : SetupEscapedComparer(comparer, context);
        }

        private SetupStatus SetupDisabledComparer<T>(T comparer, ICompareContext context) where T: IObjectComparer
        {
            return _settings.IsDisabledComparer(comparer.Name) ? SetupStatus.Disable : SetupStatus.Default;
        }
        
        private SetupStatus SetupEscapedComparer(IObjectComparer comparer, ICompareContext context)
        {
            return _settings.IsDisabledComparer(comparer.Name) ? SetupStatus.Disable : SetupStatus.Escape;
        }
        
        private SetupStatus SetupGenericsComparer<T>(T comparer, ICompareContext context) where T: GenericComparer
        {
            comparer.IgnoreGenericInterfaces = _settings.IsDisabledGenericComparer(comparer.Name);
            return SetupDisabledComparer(comparer, context);
        }

        private SetupStatus SetupDictionaryComparer(DictionaryComparer comparer, ICompareContext context)
        {
            comparer.KeysComparer = _settings.KeysComparer;
            return SetupGenericsComparer(comparer, context);
        }

        private SetupStatus SetupPropertiesComparer(PropertiesComparer obj, ICompareContext context)
        {
            obj.IgnoreReadOnly = _settings.IsReadOnlyPropertiesIgnored;
            obj.AccessFlags = _settings.PropertiesFlags;
            return SetupDisabledComparer(obj, context);
        }

        private SetupStatus SetupFieldsComparer(FieldsComparer obj, ICompareContext context)
        {
            obj.AccessFlags = _settings.FieldsFlags;
            return SetupDisabledComparer(obj, context);
        }

        public string Name
        {
            get { return IdentifiableName; }
        }

        public IResultBuilder GetResultBuilder(ICompareContext context)
        {
            var result = _settings.GetResult();
            if (result != null)
            {
                return this.IsSameAs(result, context);
            }
            
            var comparer = _settings.GetComparer();
            if (comparer != null)
            {
                return comparer.Compare(context);
            }
            return _defaultComparer.Compare(context);
        }

        public IResultBuilder Compare(ICompareContext context)
        {
            Trace.WriteLine("Comparing: <" + context.LPath + "> and <" + context.RPath + ">.");
            var isChanged = _settings.Setup(context);           
            var builder = GetResultBuilder(context);            
            if (isChanged)
            {
                _settings.Restore();    
            }
            return builder;
        }
    }
}